// Kony


#include "Game/Base_GameModeBase.h"

#include "EngineUtils.h"
#include "Game/BaseGameInstance.h"
#include "Game/LoadScreenSaveGame.h"
#include "GameFramework/PlayerStart.h"
#include "Interaction/SaveInterface.h"
#include "Kismet/GameplayStatics.h"
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
#include "UI/ViewModel/MVVM_LoadSlot.h"

//保存游戏槽位数据函数
void ABase_GameModeBase::SaveSlotGame(UMVVM_LoadSlot* LoadSlot, int32 SlotIndex)
{
	//如果索引的槽位已存在 就删除该槽位，后续重新创建新的覆盖
	DeleteSaveSlot(LoadSlot->GetLoadSlotName(), SlotIndex);
	//使用蓝图中选择的保存游戏蓝图 创建为基础的保存游戏类对象
	USaveGame* SaveGameObject = UGameplayStatics::CreateSaveGameObject(LoadScreenSaveGameClass);
	//使用保存游戏对象 转换成 自定义保存游戏类对象
	ULoadScreenSaveGame* LoadScreenSaveGame = Cast<ULoadScreenSaveGame>(SaveGameObject);
	if (!LoadScreenSaveGame)
	{
		// 兜底：避免空指针导致崩溃
		LoadScreenSaveGame = NewObject<ULoadScreenSaveGame>();
	}
	LoadScreenSaveGame->SlotName = LoadSlot->GetLoadSlotName(); //存储槽位名称
	LoadScreenSaveGame->PlayerName = LoadSlot->GetPlayerName(); //存储玩家昵称
	LoadScreenSaveGame->MapName = LoadSlot->GetMapName(); //存储地图名称
	LoadScreenSaveGame->SlotIndex = SlotIndex; //存储槽位索引
	LoadScreenSaveGame->SaveSlotStatus = Taken; //槽位状态 标记为已占用
	LoadScreenSaveGame->PlayerStartTag = LoadSlot->PlayerStartTag; //存储玩家起始标签，也就是最后一次存档玩家的位置

	//保存该槽位
	UGameplayStatics::SaveGameToSlot(LoadScreenSaveGame, LoadScreenSaveGame->SlotName, LoadScreenSaveGame->SlotIndex);
}
//获取加载游戏槽位数据函数
ULoadScreenSaveGame* ABase_GameModeBase::GetSaveSlotData(const FString& SlotName, int32 SlotIndex) const
{
	//创建保存游戏对象
	USaveGame* SaveGameObject = nullptr;
	//检查索引对应的槽位是否存在,如果存在就加载,如果不存在就创建存档
	if (UGameplayStatics::DoesSaveGameExist(SlotName, SlotIndex))
	{
		SaveGameObject = UGameplayStatics::LoadGameFromSlot(SlotName, SlotIndex);
	}
	else
	{
		SaveGameObject = UGameplayStatics::CreateSaveGameObject(LoadScreenSaveGameClass);
	}
	//转换成自定义保存游戏类对象
	ULoadScreenSaveGame* LoadScreenSaveGame = Cast<ULoadScreenSaveGame>(SaveGameObject);
	if (!LoadScreenSaveGame)
	{
		// 兜底：避免空指针，返回一个默认对象
		LoadScreenSaveGame = NewObject<ULoadScreenSaveGame>();
	}
	return LoadScreenSaveGame;
	
}
//删除存档槽位函数
void ABase_GameModeBase::DeleteSaveSlot(const FString& SlotName, int32 SlotIndex)
{
	//删除本地存档
	if (UGameplayStatics::DoesSaveGameExist(SlotName, SlotIndex))
	{
		UGameplayStatics::DeleteGameInSlot(SlotName, SlotIndex);
	}
}
//获取当前游戏保存数据
ULoadScreenSaveGame* ABase_GameModeBase::RetrieveInGameSaveData()
{
	//获取游戏实例
	UBaseGameInstance* BaseGameInstance = Cast<UBaseGameInstance>(GetGameInstance());
	const FString InGameLoadSlotName = BaseGameInstance->LoadSlotName; //获取加载槽名称
	const int32 InGameSlotIndex = BaseGameInstance->LoadSlotIndex; //获取加载槽索引
	return GetSaveSlotData(InGameLoadSlotName, InGameSlotIndex);
}
//保存游戏进度数据函数
void ABase_GameModeBase::SaveInGameProgressData(ULoadScreenSaveGame* SaveObject)
{
	UBaseGameInstance* BaseGameInstance = Cast<UBaseGameInstance>(GetGameInstance());
	const FString InGameLoadSlotName = BaseGameInstance->LoadSlotName; //获取加载槽名称
	const int32 InGameSlotIndex = BaseGameInstance->LoadSlotIndex; //获取加载槽索引
	//保存玩家起始标签
	BaseGameInstance->PlayerStartTag = SaveObject->PlayerStartTag;
	//保存游戏
	UGameplayStatics::SaveGameToSlot(SaveObject, InGameLoadSlotName, InGameSlotIndex);
}
//保存游戏世界状态
void ABase_GameModeBase::SaveWorldState(UWorld* World, const FString& DestinationMapAssetName) const
{
	//获取当前地图名称
	FString WorldName = World->GetMapName();
	//移除地图名称中的流关卡前缀，确保获得纯净的地图名称
	WorldName.RemoveFromStart(World->StreamingLevelsPrefix);
	//获取游戏实例
	UBaseGameInstance* BaseGI = Cast<UBaseGameInstance>(GetGameInstance());
	check(BaseGI);
	//获取当前进行的游戏角色存档
	if (ULoadScreenSaveGame* SaveGame = GetSaveSlotData(BaseGI->LoadSlotName, BaseGI->LoadSlotIndex))
	{
		//如果目标地图资源名称不为空 则为跳转到新地图保存
		if (DestinationMapAssetName != FString(""))
		{
			//保存目标地图
			SaveGame->MapAssetName = DestinationMapAssetName;
			//通过地图资产名称获取地图名称
			SaveGame->MapName = GetMapNameFromMapAssetName(DestinationMapAssetName);
		}
		//检查当前地图是否已存储
		if (!SaveGame->HasMap(WorldName))
		{
			//如果未存储当前地图 则创建新地图结构体进行存储
			FSavedMap NewSavedMap;
			NewSavedMap.MapAssetName = WorldName;
			//存储到存档
			SaveGame->SavedMaps.Add(NewSavedMap);
		}
		//获取存储的当前世界
		FSavedMap SavedMap = SaveGame->GetSavedMapWithMapName(WorldName);
		//清空存储的地图演员数组 这个是为了安全考虑 后续重新添加 “actors” 并不是Actor本身 而是存储Actor信息的结构体
		SavedMap.SavedActors.Empty();
		//遍历世界中的所有 Actor
		//TODO:这里可以优化成创建一个需要存储的Actor数组 然后遍历这个数组
		for (TActorIterator<AActor> It(World); It; ++It)
		{
			AActor* Actor = *It;
			//判断演员是否有效 并实现了保存游戏接口 ，否则跳过
			if (!IsValid(Actor)|| !Actor->Implements<USaveInterface>()) continue;
			FSavedActor SavedActor;
			SavedActor.ActorName = Actor->GetName(); //获取演员名称
			SavedActor.Transform = Actor->GetActorTransform(); //获取演员位置旋转
			//内存写入器 - 初始化存入Actor字节的模板（FMemoryWriter 是一个带有索引大小的模板类 支持32和64的序列化数据模板）
			FMemoryWriter MemoryWriter(SavedActor.Bytes);
			//创建一个代理存档对象，用于将 UObject 和其名称序列化为字符串
			//第二个参数设为 true 表示这是用于保存的操作
			FObjectAndNameAsStringProxyArchive Archive(MemoryWriter, true);
			Archive.ArIsSaveGame = true; //设置为保存游戏
			Actor->Serialize(Archive); //序列化演员
			SavedMap.SavedActors.AddUnique(SavedActor); //添加演员数组 用AddUnique确保唯一性
		}
		//遍历已保存的Map
		for (FSavedMap& MapToReplace : SaveGame->SavedMaps)
		{
			if (MapToReplace.MapAssetName == WorldName)
			{
				//找到对应的Map 覆盖保存
				MapToReplace = SavedMap;
			}
		}
		//保存到角色存档中
		UGameplayStatics::SaveGameToSlot(SaveGame, BaseGI->LoadSlotName, BaseGI->LoadSlotIndex);
	}
}
//加载游戏世界状态
void ABase_GameModeBase::LoadWorldState(UWorld* World) const
{
	//获取当前地图名称
	FString WorldName = World->GetMapName();
	//移除地图名称中的流关卡前缀，确保获得纯净的地图名称
	WorldName.RemoveFromStart(World->StreamingLevelsPrefix);
	//获取游戏实例
	UBaseGameInstance* BaseGI = Cast<UBaseGameInstance>(GetGameInstance());
	check(BaseGI);
	//检查磁盘存档 指定名称和索引的存档文件是否存在
	if (UGameplayStatics::DoesSaveGameExist(BaseGI->LoadSlotName, BaseGI->LoadSlotIndex))
	{
		//从对应槽位读取出存档
		ULoadScreenSaveGame* SaveGame = Cast<ULoadScreenSaveGame>(UGameplayStatics::LoadGameFromSlot(BaseGI->LoadSlotName, BaseGI->LoadSlotIndex));
		//判断存档是否为null 如果为null 则提示UELOG 提示未能加载存档
		if (SaveGame == nullptr)
		{
			UE_LOG(LogTemp, Warning, TEXT("Could not load save game 未能加载存档"));
			return;
		}
		
		//遍历世界中的所有 Actor
		//TODO:这里可以优化成创建一个需要存储的Actor数组 然后遍历这个数组
		for (TActorIterator<AActor> It(World); It; ++It)
		{
			AActor* Actor = *It;
			//判断Actor是否实现了保存接口
			if (!IsValid(Actor)|| !Actor->Implements<USaveInterface>()) continue;
			//通过地图名称获取保存的Actor数组
			for (FSavedActor SavedActor: SaveGame->GetSavedMapWithMapName(WorldName).SavedActors)
			{
				if (SavedActor.ActorName == Actor->GetName())
				{
					//判断是否保存了变换
					if (ISaveInterface::Execute_ShouldLoadTransform(Actor))
					{
						Actor->SetActorTransform(SavedActor.Transform);//更新变换位置
					}
					//创建内存读取器
					FMemoryReader MemoryReader(SavedActor.Bytes);
					FObjectAndNameAsStringProxyArchive Archive(MemoryReader, true);
					Archive.ArIsSaveGame = true;
					//这里是读取并反序列化,从字节转换为变量
					Actor->Serialize(Archive); //converts binary bytes back into variables
					//执行加载Actor接口函数 会触发每个保存的演员对应加载接口
					ISaveInterface::Execute_LoadActor(Actor);
				}
			}
		}
	}
}

//跳转地图 传入加载槽视图模型
void ABase_GameModeBase::TravelToMap(UMVVM_LoadSlot* LoadSlot)
{
	const FString SlotName = LoadSlot->GetLoadSlotName(); //插槽名称
	const int32 SlotIndex = LoadSlot->SlotIndex; //插槽索引
	//跳转指定地图
	UGameplayStatics::OpenLevelBySoftObjectPtr(this, Maps.FindChecked(LoadSlot->GetMapName()));
}
//根据地图资源名称获取地图名称
FString ABase_GameModeBase::GetMapNameFromMapAssetName(const FString& MapAssetName) const
{
	for (const TPair<FString, TSoftObjectPtr<UWorld>>& Map : Maps)
	{
		//通过软对象指针获取资源名称
		if (Map.Value.ToSoftObjectPath().GetAssetFName() == MapAssetName)
		{
			return Map.Key;
		}
	}
	return FString();
}

//重写选择玩家开始位置函数，作用是获取玩家进入游戏的位置
AActor* ABase_GameModeBase::ChoosePlayerStart_Implementation(AController* Player)
{
	//获取游戏实例
	UBaseGameInstance* BaseGameInstance = Cast<UBaseGameInstance>(GetGameInstance());
	//创建所有演员数组变量
	TArray<AActor*> Actors;
	//获取所有演员,赋值到actors数组变量
	UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), Actors);
	//判断数组不等于0
	if (Actors.Num() > 0)
	{
		//默认取第一个，如果后续没找到指定的 就在第一个生成玩家
		AActor* SelectedActor = Actors[0];
		for (AActor* Actor : Actors)
		{
			//转换成玩家开始位置
			if (APlayerStart* PlayerStart = Cast<APlayerStart>(Actor))
			{
				//判断如果玩家开始位置的标签等于 游戏实例中玩家指定标签就赋值
				if (PlayerStart->PlayerStartTag == BaseGameInstance->PlayerStartTag)
				{
					SelectedActor = PlayerStart;
					break;
				}
			}
		}
		return SelectedActor;
	}
	return nullptr;
}

void ABase_GameModeBase::BeginPlay()
{
	Super::BeginPlay();
	//添加默认地图
	Maps.Add(DefaultMapName, DefaultMap);
}
